package com.sdkj.dspservice.modules.report.utils.entity;

import com.alibaba.druid.pool.DruidDataSource;
import com.sdkj.dspservice.modules.report.utils.SourceException;
import com.sdkj.dspservice.modules.report.utils.SourceUtils;
import com.sdkj.dspservice.common.enums.DataTypeEnum;
import com.sdkj.dspservice.common.utils.StringUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static com.sdkj.dspservice.common.constant.Consts.JDBC_DATASOURCE_DEFAULT_VERSION;

/**
 * jdbc数据源
 *
 * @author tm
 */
@Slf4j
@Component
public class JdbcDataSource {

    private static final Object lockLock = new Object();
    private static volatile Map<String, DruidDataSource> dataSourceMap = new ConcurrentHashMap<>();
    private static volatile Map<String, Lock> dataSourceLockMap = new ConcurrentHashMap<>();

    @Value("${spring.datasource.dynamic.druid.maxActive:8}")
    @Getter
    protected int maxActive;

    @Value("${spring.datasource.dynamic.druid.initialSize:0}")
    @Getter
    protected int initialSize;

    @Value("${spring.datasource.dynamic.druid.minIdle:1}")
    @Getter
    protected int minIdle;

    @Value("${spring.datasource.dynamic.druid.maxWait:60000}")
    @Getter
    protected long maxWait;

    @Value("${spring.datasource.dynamic.druid.timeBetweenEvictionRunsMillis}")
    @Getter
    protected long timeBetweenEvictionRunsMillis;

    @Value("${spring.datasource.dynamic.druid.minEvictableIdleTimeMillis}")
    @Getter
    protected long minEvictableIdleTimeMillis;

    @Value("${spring.datasource.dynamic.druid.maxEvictableIdleTimeMillis}")
    @Getter
    protected long maxEvictableIdleTimeMillis;

    @Value("${spring.datasource.dynamic.druid.testWhileIdle}")
    @Getter
    protected boolean testWhileIdle;

    @Value("${spring.datasource.dynamic.druid.testOnBorrow}")
    @Getter
    protected boolean testOnBorrow;

    @Value("${spring.datasource.dynamic.druid.testOnReturn}")
    @Getter
    protected boolean testOnReturn;

    @Value("${spring.datasource.dynamic.druid.breakAfterAcquireFailure:true}")
    @Getter
    protected boolean breakAfterAcquireFailure;

    @Value("${spring.datasource.dynamic.druid.connectionErrorRetryAttempts:1}")
    @Getter
    protected int connectionErrorRetryAttempts;

    @Value("${spring.datasource.dynamic.druid.keepAlive:false}")
    @Getter
    protected boolean keepAlive;

    @Value("${spring.datasource.dynamic.druid.validationQueryTimeout:5}")
    @Getter
    protected int validationQueryTimeout;

    @Value("${spring.datasource.dynamic.druid.validationQuery}")
    @Getter
    protected String validationQuery;

    private Lock getDataSourceLock(String key) {
        if (dataSourceLockMap.containsKey(key)) {
            return dataSourceLockMap.get(key);
        }

        synchronized (lockLock) {
            if (dataSourceLockMap.containsKey(key)) {
                return dataSourceLockMap.get(key);
            }
            Lock lock = new ReentrantLock();
            dataSourceLockMap.put(key, lock);
            return lock;
        }
    }

    private String getDataSourceKey(JdbcSourceInfo jdbcSourceInfo) {
        return SourceUtils.getKey(jdbcSourceInfo.getJdbcUrl(),
                jdbcSourceInfo.getUsername(),
                jdbcSourceInfo.getPassword(),
                jdbcSourceInfo.getDbVersion(),
                jdbcSourceInfo.isExt());
    }

    /**
     * 获取数据源
     *
     * @param jdbcSourceInfo
     * @return
     */
    
    public DruidDataSource getDataSource(JdbcSourceInfo jdbcSourceInfo) {
        boolean ext = jdbcSourceInfo.isExt();
        String jdbcUrl = jdbcSourceInfo.getJdbcUrl();
        String username = jdbcSourceInfo.getUsername();
        String password = jdbcSourceInfo.getPassword();
        String dbVersion = jdbcSourceInfo.getDbVersion();
        // 根据数据源参数生成key
        String key = getDataSourceKey(jdbcSourceInfo);
        // 根据key，从map中获取数据源，如果有则直接返回
        DruidDataSource druidDataSource = dataSourceMap.get(key);
        if (druidDataSource != null && !druidDataSource.isClosed()) {
            return druidDataSource;
        }
        // 根据key，获取锁对象
        Lock lock = getDataSourceLock(key);
        try {
            // 获取该线程锁
            if (!lock.tryLock(30L, TimeUnit.SECONDS)) {
                // 没获取到锁，重试一下是否能从map中获取该链接，能则直接返回，不能则抛出异常
                druidDataSource = dataSourceMap.get(key);
                if (druidDataSource != null && !druidDataSource.isClosed()) {
                    return druidDataSource;
                }
                throw new SourceException("Unable to get datasource for jdbcUrl: " + jdbcUrl);
            }
        } catch (InterruptedException e) {
            throw new SourceException("Unable to get datasource for jdbcUrl: " + jdbcUrl);
        }
        // 此时已获取到该锁
        // 从map中尝试获取链接
        druidDataSource = dataSourceMap.get(key);
        if (druidDataSource != null && !druidDataSource.isClosed()) {
            // 获取到，则释放锁，并返回
            lock.unlock();
            return druidDataSource;
        }
        // 没有从map中获取到链接，则手动新建
        druidDataSource = new DruidDataSource();
        try {
            // 插入驱动类名称
            if (StringUtils.isEmpty(dbVersion) || !ext || JDBC_DATASOURCE_DEFAULT_VERSION.equals(dbVersion)) {
                // 当版本为空或为默认时，从url中获取驱动类名称
                String className = SourceUtils.getDriverClassName(jdbcUrl, null);
                try {
                    System.out.println("****************************************************");
                    System.out.println(className);
                    System.out.println("****************************************************");
                    Class.forName(className);
                } catch (ClassNotFoundException e) {
                    throw new SourceException("Unable to get driver instance for jdbcUrl: " + jdbcUrl);
                }
                druidDataSource.setDriverClassName(className);
            } else {
                // 版本不为空时
//                druidDataSource.setDriverClassName(CustomDataSourceUtils.getInstance(jdbcUrl, dbVersion).getDriver());
//                String path = System.getenv("DAVINCI3_HOME") + File.separator + String.format(Consts.PATH_EXT_FORMATTER, jdbcSourceInfo.getDatabase(), dbVersion);
//                druidDataSource.setDriverClassLoader(ExtendedJdbcClassLoader.getExtJdbcClassLoader(path));
            }

            druidDataSource.setUrl(jdbcUrl);
            druidDataSource.setUsername(username);

            if (!jdbcUrl.toLowerCase().contains(DataTypeEnum.PRESTO.getFeature())) {
                druidDataSource.setPassword(password);
            }

            druidDataSource.setInitialSize(initialSize);
            druidDataSource.setMinIdle(minIdle);
            druidDataSource.setMaxActive(maxActive);
            druidDataSource.setMaxWait(maxWait);
            druidDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
            druidDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
            druidDataSource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
            druidDataSource.setTestWhileIdle(testWhileIdle);
            druidDataSource.setTestOnBorrow(testOnBorrow);
            druidDataSource.setTestOnReturn(testOnReturn);
            druidDataSource.setConnectionErrorRetryAttempts(connectionErrorRetryAttempts);
            druidDataSource.setBreakAfterAcquireFailure(breakAfterAcquireFailure);
            druidDataSource.setKeepAlive(keepAlive);
            druidDataSource.setValidationQueryTimeout(validationQueryTimeout);
            druidDataSource.setValidationQuery(validationQuery);
            druidDataSource.setRemoveAbandoned(true);
            druidDataSource.setRemoveAbandonedTimeout(3600 + 5 * 60);
            druidDataSource.setLogAbandoned(true);

            // default validation query
            String driverName = druidDataSource.getDriverClassName();
            if (driverName.indexOf("sqlserver") != -1 || driverName.indexOf("mysql") != -1
                    || driverName.indexOf("h2") != -1 || driverName.indexOf("moonbox") != -1) {
                druidDataSource.setValidationQuery("select 1");
            }

            if (driverName.indexOf("oracle") != -1) {
                druidDataSource.setValidationQuery("select 1 from dual");
            }

//            if (!CollectionUtils.isEmpty(jdbcSourceInfo.getProperties())) {
//                Properties properties = new Properties();
//                jdbcSourceInfo.getProperties().forEach(dict -> properties.setProperty(dict.getKey(), dict.getValue()));
//                druidDataSource.setConnectProperties(properties);
//            }

            try {
//                druidDataSource.setFilters(filters);
                druidDataSource.init();
            } catch (Exception e) {
                log.error("Exception during pool initialization", e);
                throw new SourceException(e.getMessage());
            }

            dataSourceMap.put(key, druidDataSource);

        } finally {
            lock.unlock();
        }

        return druidDataSource;
    }

    /**
     * 释放数据源
     *
     * @param jdbcSourceInfo
     */
    public void removeDatasource(JdbcSourceInfo jdbcSourceInfo) {
        String key = getDataSourceKey(jdbcSourceInfo);
        Lock lock = getDataSourceLock(key);
        if (!lock.tryLock()) {
            return;
        }
        try {
            DruidDataSource druidDataSource = dataSourceMap.remove(key);
            if (druidDataSource != null) {
                druidDataSource.close();
            }
            dataSourceLockMap.remove(key);
        } finally {
            lock.unlock();
        }
    }
}
